package com.wei.czz.framework.common.service.impl;

import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.property.AesProperty;
import com.wei.czz.common.utils.DataUtils;
import com.wei.czz.framework.common.service.AesService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2023-02-28 14:15:53
 * className: AesServiceImpl AES加解密服务接口实现类
 * version: 1.0
 * description:
 */
@Service("aesService")
public class AesServiceImpl implements AesService {

    private static final Logger log = LoggerFactory.getLogger(AesServiceImpl.class);

    private final AesProperty aesProperty;

    /**
     * 存储所有单个十六进制数字（0~15）的数组
     */
    private char[] charArray;

    /**
     * 存储十六进制数字对应十进制数字映射关系的Map对象
     */
    private Map<Character, Integer> map;

    /**
     * 字符表
     */
    private char[] chars;

    public AesServiceImpl(AesProperty aesProperty) {
        this.aesProperty = aesProperty;
    }

    /**
     * 初始化加密和解密对象
     */
    @PostConstruct
    private void initCipher() {
        this.charArray = "0123456789abcdef".toCharArray();
        map = new HashMap<>(32);
        for (int i = 0; i < this.charArray.length; i++) {
            map.put(this.charArray[i], i);
        }
        chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
    }

    @Override
    public String encrypt(String plainText, String secretKey, String ivValue, boolean cipherType) {
        // 生成加密对象
        final Cipher encryptCipher = this.getCipher(Cipher.ENCRYPT_MODE, secretKey, ivValue);
        if (encryptCipher == null) {
            log.error("生成加密对象失败，无法进行加密");
            throw new CzzException();
        }
        try {
            // 执行明文加密操作
            byte[] bytes = encryptCipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));

            String cipherText;
            if (cipherType) {
                // 将字节数组转换成十六进制数字
                cipherText = bytesToHex(bytes);
            } else {
                // 将密文字节数组转换成Base64密文
                cipherText = Base64.getEncoder().encodeToString(bytes);
            }

            log.debug("AES加密，CBC模式加密原文（{}）得到十六进制数字密文（{}）", plainText, cipherText);

            return cipherText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            // 加密操作异常
            log.error("明文（{}）,加密异常。message={}", plainText, e.getMessage(), e);
            return null;
        }
    }

    @Override
    public String decrypt(String cipherText, String secretKey, String ivValue, boolean cipherType) {
        // 生成解密对象
        final Cipher decodeCipher = this.getCipher(Cipher.DECRYPT_MODE, secretKey, ivValue);
        if (Objects.isNull(decodeCipher)) {
            log.error("生成解密对象失败，无法进行解密，返回原文");
            throw new CzzException();
        }
        try {
            byte[] bytes;
            if (cipherType) {
                // 将十六进制密文转换成字节数组
                bytes = hexToBytes(cipherText);
            } else {
                // 将Base64密文转换成字节数组
                bytes = Base64.getDecoder().decode(cipherText);
            }

            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);
            // 得到解密后的字符串信息
            String plainText = new String(bytes, StandardCharsets.UTF_8);

            log.debug("AES解密，CBC模式解密密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            // AES解密异常
            log.error("密文（{}）解密异常。message={}", cipherText, e.getMessage());
            return null;
        }
    }

    @Override
    public String encryptToHex(String plainText) {
        // 生成加密对象
        final Cipher encryptCipher = this.getCipher(Cipher.ENCRYPT_MODE, aesProperty.getSecret(),
                aesProperty.getIvValue());
        if (encryptCipher == null) {
            log.error("加密对象为空，无法进行加密，返回原文");
            return plainText;
        }
        try {
            // 执行明文加密操作
            byte[] bytes = encryptCipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            // 将字节数组转换成十六进制数字
            String cipherText = bytesToHex(bytes);

//            log.debug("AES加密，CBC模式加密原文（{}）得到十六进制数字密文（{}）", plainText, cipherText);

            return cipherText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            // 加密操作异常
            log.error("明文（{}）AES加密异常。message={}", plainText, e.getMessage(), e);
        }
        return null;
    }

    @Override
    public String decodeHex(String cipherText) {
        // 生成加密对象
        final Cipher decodeCipher = this.getCipher(Cipher.DECRYPT_MODE, aesProperty.getSecret(),
                aesProperty.getIvValue());
        if (decodeCipher == null) {
            log.error("解密对象为空，无法进行解密，返回原文");
            return cipherText;
        }
        try {
            // 将十六进制数字转换成字节数组密文
            byte[] bytes = hexToBytes(cipherText);
            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);
            // 得到解密后的字符串信息
            String plainText = new String(bytes, StandardCharsets.UTF_8);

//            log.debug("AES解密，CBC模式解密十六进制数字密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            // AES解密异常
            log.error("十六进制密文（{}）AES解密异常。message={}", cipherText, e.getMessage(), e);
        }
        return null;
    }

    @Override
    public String subDecodeHex(String cipherText) {
        // 生成加密对象
        final Cipher decodeCipher = this.getCipher(Cipher.DECRYPT_MODE, aesProperty.getSecret(),
                aesProperty.getIvValue());
        if (decodeCipher == null) {
            log.error("解密对象为空，无法进行解密，返回原文");
            return cipherText;
        }
        try {
            // 将十六进制数字转换成字节数组密文
            byte[] bytes = hexToBytes(cipherText);
            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);
            // 得到解密后的字符串信息
            String plainText = new String(bytes, StandardCharsets.UTF_8);
            // 数据脱敏
            plainText = DataUtils.dataMask(plainText);
//            log.debug("AES解密，CBC模式解密十六进制数字密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            // AES解密异常
            log.error("十六进制密文（{}）AES解密异常。message={}", cipherText, e.getMessage(), e);
        }
        return null;
    }

    @Override
    public String encryptToBase64(String plainText) {
        // 生成加密对象
        final Cipher encryptCipher = this.getCipher(Cipher.ENCRYPT_MODE, aesProperty.getSecret(),
                aesProperty.getIvValue());
        if (encryptCipher == null) {
            log.error("加密对象为空，无法进行加密，返回原文");
            return plainText;
        }
        try {
            // 执行明文加密操作
            byte[] bytes = encryptCipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            // 将密文字节数组转换成Base64密文
            String cipherText = Base64.getEncoder().encodeToString(bytes);

//            log.debug("AES加密，CBC模式加密原文（{}）得到Base64密文（{}）", plainText, cipherText);

            return cipherText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            // 加密操作异常
            log.error("明文（{}）AES加密异常。message={}", plainText, e.getMessage(), e);
        }
        return null;
    }

    @Override
    public String decodeBase64(String cipherText) {
        // 生成加密对象
        final Cipher decodeCipher = this.getCipher(Cipher.DECRYPT_MODE, aesProperty.getSecret(),
                aesProperty.getIvValue());
        if (decodeCipher == null) {
            log.error("解密对象为空，无法进行解密，返回原文");
            return cipherText;
        }
        try {
            // 将Base64密文转换成字节数组密文
            byte[] bytes = Base64.getDecoder().decode(cipherText);
            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);

            // 得到解密后的字符串信息
            String plainText = new String(bytes, StandardCharsets.UTF_8);

//            log.debug("AES解密，CBC模式解密Bae64密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            // AES解密异常
            log.error("Base64密文（{}）AES解密异常。message={}", cipherText, e.getMessage(), e);
        }
        return null;
    }

    @Override
    public String subDecodeBase64(String cipherText) {
        // 生成加密对象
        final Cipher decodeCipher = this.getCipher(Cipher.DECRYPT_MODE, aesProperty.getSecret(),
                aesProperty.getIvValue());
        if (decodeCipher == null) {
            log.error("解密对象为空，无法进行解密，返回原文");
            return cipherText;
        }
        try {
            // 将Base64密文转换成字节数组密文
            byte[] bytes = Base64.getDecoder().decode(cipherText);
            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);

            // 得到解密后的字符串信息
            String plainText = new String(bytes, StandardCharsets.UTF_8);
            // 数据脱敏
            plainText = DataUtils.dataMask(plainText);
//            log.debug("AES解密，CBC模式解密Bae64密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            // AES解密异常
            log.error("Base64密文（{}）AES解密异常。message={}", cipherText, e.getMessage(), e);
        }
        return null;
    }

    @Override
    public String czzEncrypt(String plainText) {
        // 生成一个uuid
        String uuid = UUID.randomUUID().toString().replace("-", "");
        String secretKey = uuid.substring(0, uuid.length() / 2);
        String ivValue = uuid.substring(uuid.length() / 2);

        // 明文加密
        String cipherText = this.encrypt(plainText, secretKey, ivValue, true);

        // 密钥加密
        uuid = caesarEncode(uuid, cipherText.charAt(0));
        // 密钥拼接密文返回
        return uuid + Constant.SPLIT + cipherText;
    }

    @Override
    public Map<String, String> czzEncrypt(List<String> plainTextList) {
        // 生成一个uuid
        String uuid = UUID.randomUUID().toString().replace("-", "");
        String secretKey = uuid.substring(0, uuid.length() / 2);
        String ivValue = uuid.substring(uuid.length() / 2);

        Map<String, String> result = new HashMap<>();

        for (String plainText : plainTextList) {
            if (result.containsKey(plainText)) {
                continue;
            }
            // 明文加密
            String cipherText = this.encrypt(plainText, secretKey, ivValue, true);

            // 密钥加密
            uuid = caesarEncode(uuid, cipherText.charAt(0));

            // 密钥拼接密文
            cipherText = uuid + Constant.SPLIT + cipherText;

            result.put(plainText, cipherText);
        }

        return result;
    }

    @Override
    public String czzDecrypt(String cipherText) {
        boolean bool = Pattern.matches("^[0-9A-Za-z]{32}-[0-9a-f]+$", cipherText);
        if (!bool) {
            log.error("密文格式不正确，请确认。cipherText={}", cipherText);
            throw new CzzException("密文格式不正确，解密失败");
        }
        String secretKey;
        String ivValue;
        try {
            String[] arr = cipherText.split(Constant.SPLIT);
            String uuid = arr[0];
            cipherText = arr[1];

            int moveNum = cipherText.charAt(0);

            // 解密
            uuid = caesarDecode(uuid, moveNum);
            secretKey = uuid.substring(0, uuid.length() / 2);
            ivValue = uuid.substring(uuid.length() / 2);

        } catch (Exception e) {
            log.error("从密文中获取密钥、偏移量异常。cipherText={} message={}", cipherText, e.getMessage(), e);
            throw new CzzException();
        }
        // 解密
        return this.decrypt(cipherText, secretKey, ivValue, true);
    }

    @Override
    public Map<String, String> czzDecrypt(List<String> cipherTextList) {
        Map<String, String> result = new HashMap<>();
        for (String cipherText : cipherTextList) {
            // 解密
            String plainText = this.czzDecrypt(cipherText);

            result.put(cipherText, plainText);
        }
        return result;
    }

    /**
     * 构造AES加解密对象
     */
    private Cipher getCipher(int type, String secret, String ivValue) {
        try {
            // 获取密钥的字节数组
            byte[] bytes = this.optimizeSecretKey(secret);
            // 获取密钥对象
            /*
                加密方式
             */
            String encryptType = "AES";
            // 创建密钥对象
            SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, encryptType);
            /*
                AES加密方法，CBC加密模式，PKCS5Padding补码方式
             */
            String cipherType = "AES/CBC/PKCS5Padding";
            // 获取加密执行对象
            Cipher cipher = Cipher.getInstance(cipherType);
            // 创建向量iv，增加加密强度
            IvParameterSpec ivParameterSpec = new IvParameterSpec(this.optimizeIvValue(ivValue));
            // 初始化加密执行对象
            cipher.init(type, secretKeySpec, ivParameterSpec);

            return cipher;
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            // 获取加解密方式异常
            log.error("获取AES加解密对象异常。secret={} ivValue={} message={}", secret, ivValue, e.getMessage(), e);
        }  catch (InvalidAlgorithmParameterException | InvalidKeyException e) {
            // 初始化加解密对象异常
            log.error("初始化AES加密对象异常。secret={} ivValue={} message={}", secret, ivValue, e.getMessage(), e);
        }
        return null;
    }

    /**
     * 将字节数组转换成十六进制数字
     * @param plainText 待转换的字节数组
     * @return 转换完成的十六进制数字
     */
    private String bytesToHex(byte[] plainText) {
        StringBuilder sb = new StringBuilder();
        int bit;
        for (byte b : plainText) {
            bit = (b & 0x0f0) >> 4;
            sb.append(charArray[bit]);
            bit = b & 0x0f;
            sb.append(charArray[bit]);
        }
        return sb.toString().trim();
    }

    /**
     * 将十六进制数字转换成字节数组
     * @param cipherText 待转换的十六进制数字
     * @return 转换完成的字节数组
     */
    private byte[] hexToBytes(String cipherText) {
        char[] hexArray = cipherText.toCharArray();
        byte[] result = new byte[cipherText.length() / 2];
        int n, index, length = result.length;
        for (int i = 0; i < length; i++) {
            index = 2 * i;
            n = map.getOrDefault(hexArray[index], -1) * 16;
            n += map.getOrDefault(hexArray[index + 1], -1);
            result[i] = (byte) (n & 0xFF);
        }
        return result;
    }

    /**
     * 凯撒加密优化版本
     * 该版本仅限于加解密特定字符
     * @param plainText 明文
     * @param moveNum   移动位数
     * @return 密文
     */
    public String caesarEncode(String plainText, int moveNum) {
        int length = plainText.length();
        StringBuilder sb = new StringBuilder(length);
        moveNum %= chars.length;
        for (int i = 0; i < length; i++) {
            int c = plainText.charAt(i);
            // 获取字符在字符表的下标
            int index = this.getIndex(c);
            // 位置移动
            index += moveNum;
            // 位置只能落在数组大小范围内
            index %= chars.length;
            // 存储移位结果
            sb.append(chars[index]);
        }
        return sb.toString();
    }

    /**
     * 凯撒加密优化版本
     * 该版本仅限于加解密特定字符
     * @param cipherText 密文
     * @param moveNum    移动位数
     * @return 明文
     */
    public String caesarDecode(String cipherText, int moveNum) {
        int length = cipherText.length();
        StringBuilder sb = new StringBuilder(length);
        moveNum %= chars.length;
        for (int i = 0; i < length; i++) {
            int c = cipherText.charAt(i);
            // 获取字符在字符表的下标
            int index = this.getIndex(c);
            // 位置移动
            index -= moveNum;
            // 位置只能落在数组大小范围内
            if (index < 0) {
                index += chars.length;
            }
            // 存储移位结果
            sb.append(chars[index]);
        }
        return sb.toString();
    }

    /**
     * 获取字符在字符表的下标
     * @param c 字符数值
     * @return 下标
     */
    private int getIndex(int c) {
        if (c > 47 && c < 58) {
            // 获取数字在字符表中的下标
            return c % 48;
        } else if (c > 64 && c < 91) {
            // 获取小写字母在字符表中的下标
            return c % 65 + 10;
        } else if (c > 96 && c < 123) {
            // 获取大写字母在字符表中的下标
            return c % 97 + 36;
        } else {
            log.info("参数字符不符合要求。c={}", (char) c);
            throw new CzzException("字符不符合条件");
        }
    }

    /**
     * 优化密钥
     * @param secretKey 密钥
     * @return 优化后的密钥
     */
    private byte[] optimizeSecretKey(String secretKey) {
        int secretLength = secretKey.length();
        if (secretLength == 16 || secretLength == 24 || secretLength == 32) {
            // 如果密钥长度等于16、24、32，直接返回密钥
            return secretKey.getBytes(StandardCharsets.UTF_8);
        }
        if (secretLength < 16) {
            // 如果密钥长度小于16，按照16长度生成密钥
            return Arrays.copyOf(secretKey.getBytes(StandardCharsets.UTF_8), 16);
        } else if (secretLength < 24) {
            // 如果密钥长度小于24，按照24长度生成密钥
            return Arrays.copyOf(secretKey.getBytes(StandardCharsets.UTF_8), 24);
        } else {
            // 如果密钥长度大于24，按照32长度生成密钥
            return Arrays.copyOf(secretKey.getBytes(StandardCharsets.UTF_8), 32);
        }
    }

    /**
     * 优化偏移量
     * @param ivValue 偏移量
     * @return 优化后的偏移量
     */
    private byte[] optimizeIvValue(String ivValue) {
        int ivLength = ivValue.length();
        if (ivLength == 16) {
            // 如果偏移量长度等于16，直接返回偏移量
            return ivValue.getBytes(StandardCharsets.UTF_8);
        }
        // 如果偏移量长度不等于16，按照16长度生成偏移量
        return Arrays.copyOf(ivValue.getBytes(StandardCharsets.UTF_8), 16);
    }
}
